feat(brand-protocol): ratify distributed brand.json + fold + typed trademarks#4505
Merged
Conversation
Draft RFC proposing per-brand canonical brand.json documents linked by mutual-assertion pointers, replacing the monolithic-with-inline-children shape. Hosting (static / CDN / brand-agent / AAO / self) stays an implementation choice independent of the data model. Key proposed changes (subject to discussion): - Each brand publishes one canonical brand.json owning its own attributes - New `house` pointer for declaring the immediate parent (multi-level chains via recursion) - New `brand_refs[]` (pointer-only) replacing inline `brands[]` content - New `house_attributes` block for inheritable house-wide metadata - Mutual-assertion as the trust primitive — child's `house` must be reciprocated by parent's `brand_refs[]` Migration path: 3.x accepts both shapes with deprecation warnings; brand-protocol 2.0 (decoupled from AdCP major) cuts over. Lives at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx under a new "Proposals" subgroup. Not yet normative — needs spec-owner sign-off before any code or schema changes land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RFC stays at docs/brand-protocol/proposals/distributed-brand-json-rfc.mdx as a working document, but isn't surfaced in the published docs nav. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ouse rename Addresses Pawel's review on PR #3533: 1. Lead with the operational pain point — Converse can't update its own logo in AdCP without editing Nike, Inc.'s file. Reframed Motivation. 2. Hybrid model — brands[] (inline, parent-owned) and brand_refs[] (pointer, child-owned) both first-class. Pull-based migration: brands[] is no longer deprecated. Sub-brands without their own domain stay inline. 3. Flat hierarchy — only the house declares ownership via brand_refs[] or brands[]. A brand cannot have its own brand_refs[]. Trust collapses to a single hop; no recursive walking. 4. Renamed child-side pointer from `house` to `parent_house` to avoid collision with the existing `house` declaration object on the root. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed example
check:owned-links treated the example URL as a real link and failed CI
when the path 404'd. Wrap the brand-domain segment as \${domain} so the
linter recognizes it as a placeholder (it skips URLs containing \${).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_domain; add managed_by Resolves the second round of expert review on PR #3533. Substantive changes: - Drop house_attributes / house_attributes_overrides / house_attributes_locked entirely. Inheritance/override semantics turned out to be muddy: if a brand could weaken a house policy, it wasn't really a house policy. House-level fields (data_subject_contestation, trademarks, authorized_operators) are already on the house schema; consumers walk house_domain to read them. Brand-level additions are just brand-level fields. - Rename child-side pointer from parent_house ({domain}) to house_domain (string). Reuses the existing #/definitions/domain pattern. Drops the proposed core/house-ref.json file. Matches the existing House Redirect's string-domain convention. - Add managed_by (string, optional) on brand_refs[] entries. House-declared delegation for grouping/discovery. Non-trust-bearing. Captures WPP/Publicis reality without reintroducing recursive trust. - Make house_domain optional on the Brand Canonical Document so standalone brands (Patagonia, Liquid Death) have a valid shape. Acquisition adds house_domain later; no migration required. - Add an explicit M&A section: existing redirect variants (House Redirect, Authoritative Location Redirect) handle reorganizations; resolution follows redirects through house_domain. - Add a clear field-resolution table: where each consumer-side question is answered. No inheritance/override, just per-brand vs per-house ownership. Trust model still single-hop, mutual-assertion via house_domain ↔ brand_refs[]. Migration still pull-based. brands[] still first-class (not deprecated). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Concrete schema additions for review against RFC #3533. Additive only — existing publishers unchanged. brand.json schema: - New variant: Brand Canonical Document. Self-published per-brand doc with `parent_house: BrandRef` pointer + optional house_attributes_overrides. Composes the existing `brand` definition via allOf so identity fields match the inline `brands[]` shape. - House Portfolio variant gains `brand_refs[]` (pointer brands, child-owned data) and `house_attributes` (house-wide inheritable attributes). Required changed from ["house","brands"] to ["house"] with anyOf at-least-one of brands[]/brand_refs[]. - Two new examples illustrating mixed inline+pointer house and a self- published Converse canonical document. docs/brand-protocol/brand-json.mdx: added a "Proposed (RFC)" callout pointing at the RFC and PR #3533. Existing four variants documented as-is. Cross-array invariant — a brand_id MUST NOT appear in both brands[] and brand_refs[] of the same house — is documented in field descriptions. JSON Schema can't express it; lint/validator follow-up needed if RFC ratifies. Status: review-only. Not normative until RFC ratifies. Marked DRAFT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…arent_house example Code-reviewer + protocol-expert findings on PR #3764: - Brand Canonical Document variant lacked oneOf disambiguation. brand definition has additionalProperties: true and the inner allOf member had no constraint, so a malformed Portfolio with a stray parent_house could silently re-type as a Canonical Document. Replaced the narrow not: {required: ["brand_refs"]} with not: {anyOf: [...]} blocking all house-only top-level keys (house, brands, brand_refs, house_attributes, authorized_operators). - Fixed example: parent_house: { domain: "nikeinc.com", brand_id: "converse" } read as "I'm pointing at converse inside nikeinc.com" — but Converse owns this document; the brand_id was the self, not the parent. parent_house is a pointer UP, only domain is meaningful for a typical house pointer. Updated both the schema example and the docs section. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…managed_by + drop house_attributes Tracks RFC #3533 v3. Replaces the v2 cut with the simplified model decided during expert review. Schema deltas (relative to previous impl cut): - Replace parent_house ($ref core/brand-ref.json) with house_domain (string, reuses #/definitions/domain). Strings match the existing House Redirect convention and drop the planned core/house-ref.json file entirely. - Make house_domain optional on Brand Canonical Document so standalone brands (Patagonia, Liquid Death) have a valid shape without spinning up a degenerate "house of one." If a standalone brand is later acquired, it adds house_domain and the new house adds it to brand_refs[]. No new variant needed. - Drop house_attributes / house_attributes_overrides entirely. Inheritance/ override semantics turned out muddy — if a brand could weaken a house policy, it wasn't really a house policy. House-level fields stay where they already are (data_subject_contestation, trademarks, authorized_operators on the house schema). Brand-level constraints are additive, not overrides. - Replace the brand_refs[] $ref to core/brand-ref.json with an inline {domain, brand_id?, managed_by?} shape. brand-ref.json's existing governance-override fields (industries, data_subject_contestation) don't belong on a house-side declaration; they're consumer-side overrides. - Add managed_by (string, optional) on brand_refs[] entries — house-declared delegation for grouping/discovery, non-trust-bearing. Captures WPP/Publicis reality (BBH manages this brand for WPP) without reintroducing recursive trust. Examples updated: - Nike Inc. mixed-shape (inline Nike SB + pointer Converse, Jordan) - WPP with managed_by (BBH Sport managed_by bbh.com, etc.) - Converse self-published with house_domain - Patagonia standalone (no house_domain) docs/brand-protocol/brand-json.mdx: rewritten Distributed Extensions section to match the new shape. Adds the four worked examples. Adds field-resolution table showing where each consumer-side question is answered (no inheritance, no overrides). Adds explicit M&A section showing existing redirect variants handle reorganizations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rmance, strictest-of compliance Addresses round-2 expert review on RFC v3: - managed_by gets normative language: MUST NOT be used for trust/auth decisions; verifiers MUST ignore for authorization. UIs SHOULD render as unilateral house claim, not aggregate cross-house. - New "Compliance fields: strictest-of resolution" section. Identity fields (logos, voice, tone) stay brand-wins. Compliance/governance fields (data_subject_contestation, compliance_policies, regulated-category flags) resolve as union/strictest of house-level and brand-level — brand cannot weaken house assertions, only add stricter constraints. - New "Standalone brands" subsection: absence of house_domain ⇒ standalone, regardless of one-sided third-party brand_refs[] claims. - New "Conformance" section formalizing brand_id cross-array uniqueness, within-array uniqueness, mutual-assertion as canonical trust primitive, managed_by non-trust, standalone trumps third-party claim, strictest-of rule, 180-day TTL. - New "Prior art" section citing IAB Tech Lab ads.txt/sellers.json reciprocal publication model — same trust shape, deployed industry pattern. No structural changes to the data model. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion + tighten not deny-list Code-reviewer round-2 nits on PR #3764: - Extract brand_refs[] item shape from inline to #/definitions/brand_ref (named type for SDK codegen / generator output). Description includes the managed_by non-trust normative language directly on the field. - Tighten Brand Canonical Document not.anyOf to also block House Redirect's region and note keys. Disambiguation against House Redirect was already clean via id+names requirement, but the deny-list now matches all four other variants' top-level fields explicitly. No example or behavior changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d brand-level trademarks Consolidates #3409 / #3533 (RFC) / #3764 (schema cut) / #3910 (fold) / #3909 (typed trademarks) into one normative PR. **Fold (#3910):** RFC content (Motivation, Conformance, strictest-of compliance resolution, Prior art) absorbed into docs/brand-protocol/brand-json.mdx as the normative spec. Proposals subdir and the standalone RFC file deleted. Variant list now reads as five variants from the top; Brand Canonical Document slots in as #5 alongside the other four. **Typed trademarks (#3909):** New #/definitions/trademark extracts the inline house-portfolio shape ({registry, number, mark}) with optional status, license_type, countries. brand definition gains trademarks: Trademark[]. House Portfolio's inline trademarks[] migrated to $ref. House-level + brand- level resolution is union (both lists are valid claims). Existing publishers are unaffected — all additions are optional, the existing four variants are unchanged, and the inline trademark shape continues to validate against the extracted definition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tightening, conformance polish Folds protocol/product/docs expert review on PR #4505. No design rollbacks; all changes tightening or expanding. Schema - Rename #/definitions/brand_ref → portfolio_entry (disambiguate from buyer-side core/brand-ref.json which identifies brands in media plans). Make brand_id required on portfolio_entry so cross-array invariant is enforceable. Add optional effective_at (ISO 8601) so consumers age edges from a publisher-anchored date. - Brand Canonical Document not.anyOf deny-list adds authoritative_location, redirect_reason, redirect_effective_at — closes ambiguous-match holes against redirect variants. - Trademark gains optional licensor_domain (when license_type=licensed_in) and nice_classes (Nice Classification for cross-industry disambiguation — Delta-airline vs Delta-faucet). Trust model - Three-tier trust table: brand-identity (TLS-only) and brand-relationships (mutual-assertion-gated) resolve separately. A leaf-only edge keeps identity trust; only relationships block. - Self-healing notification SHOULD: consumers SHOULD email the house's contact.email when they encounter a leaf-only edge, so the parent team can complete the reciprocal entry. Rate-limited per {leaf, house}. - managed_by reframed as a directory field (aggregation across houses is the intended use); MUST NOT trust kept, SHOULD NOT aggregate dropped — the latter was fiction. Conformance - New invariants: house_domain MUST NOT appear in brands[]; brand_refs[] unique by domain (not just brand_id); mutual-assertion verification MUST follow House Redirects on the house side; strictest-of compliance expanded to include policy_categories and brand-level disclaimers[]; edge-aging language reframed around effective_at rather than a fixed 180-day SHOULD. - Resolution algorithm: "resolve recursively (single hop)" → "resolve once" with explicit clause that the followed document MUST be a Brand Canonical Document. Docs / structure - Variant 4/5 cross-reference trust model forward instead of asserting it inline — variant 5 reads cleanly when first encountered. - Variant 5 field table fully enumerates top-level fields and explicitly lists prohibited fields. - New "Adopting brand_refs[] for an existing portfolio" subsection documenting the migration path and AAO registry behaviour. - New "Out of scope" subsection: JVs with two parents, PE-opacity rollups, jurisdictional governance divergence — explicitly outside brand.json's scope (brand identity ≠ corporate legal structure). - Prior art expanded with app-ads.txt and WebFinger / host-meta (RFC 7033, 6415) as IETF analogues for well-known + JSON resource discovery. - Frontmatter description and managed_by prose updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 14, 2026
Open
… headline 3.1 doesn't ship today; minor releases accumulate from changesets. This opens the 3.1 section with PR #4505 as the headline feature so the narrative space exists when subsequent 3.1 changesets land. Includes adopter-action table for the publisher-visible behaviour change (trademark string drift) and links to the four design follow-ups (#4521 verification endpoint, #4522 JV multi-parent, #4523 PE-opacity tradeoff, #4524 manager-edge reciprocation). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Consolidates the distributed-brand-json work into one normative PR.
Closes #3409, #3533, #3764, #3910, #3909.
What this lands
1. The RFC, as the normative spec (#3409, #3533, #3764, #3910)
brand.jsongains a fifth variant — Brand Canonical Document (self-published per-brand document with optionalhouse_domainpointer up).brand_refs[](pointer brands, child-owned canonical docs) alongsidebrands[](inline, parent-owned).managed_byonbrand_refs[]entries — house-declared, non-trust-bearing, for grouping/discovery.#/definitions/brand_ref(extracted for SDK codegen).["house", "brands"]to["house"]withanyOfrequiring at least one ofbrands[]/brand_refs[].house_domain↔ house'sbrand_refs[]). Standalone trumps any third-party claim. Identity is brand-wins; compliance/governance is strictest-of.docs/brand-protocol/brand-json.mdx. The standaloneproposals/distributed-brand-json-rfc.mdxfile is deleted.2. Typed brand-level trademarks (#3909)
#/definitions/trademark—{registry, number, mark}plus optionalstatus,license_type,countries.#/definitions/brandgainstrademarks: Trademark[]so inline brands AND Brand Canonical Documents can typed-publish their own marks.trademarks[]migrated to$ref— backwards compatible, validates the same shape.Compatibility
All additions are optional. The existing four variants are unchanged. Existing house-portfolio publishers using untyped trademarks continue to validate against the now-typed
Trademarkdefinition (same required fields). No migration required.Validation
npm run build:schemascleannpm run test:schemas7/7npm run test:examples36/36npm run test:json-schema260/260npm run test:composed40/40npm run typecheckcleannpm run check:owned-linkscleannpm run lint:schema-linkscleanReplaces
This PR replaces the draft #3764 (schema cut) and #3533 (RFC docs PR) — both can close once this lands. #3910 and #3909 are addressed inline.
🤖 Generated with Claude Code